/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.base;

import org.webos.java.base.fs.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.urchr.web.db.DBSession;
import org.webos.java.base.Session;
import org.webos.java.base.db.fs.DBFile;
import org.webos.java.base.fs.vfs.FolderFileType;
import org.webos.java.base.fs.vfs.VFSFileProvider;
import org.webos.java.base.fs.vfs.VFSFileType;
import org.webos.java.root.Log;
import org.webos.java.root.RandomGUID;

/**
 *
 * @author Kostya
 */
public class DBFileProvider implements FileProvider{

    private Session session = null;

    protected DBFileProvider(Session aSession) {
        session = aSession;
    }

    public static String getIcon(FileDescriptor aDescriptor){
        FileType ft = FileManager.getFileType(aDescriptor.getType());
        if(ft!=null)
            aDescriptor.setIcon(ft.getIcon());
        else
            aDescriptor.setIcon("explorer_icn explorer_icn_unknown");
        return aDescriptor.getIcon();
    }

    public FileDescriptor[] listFiles(FileDescriptor aDescriptor) throws FileException{
        if(VFSFileType.defaultInstance.getCode().equals(aDescriptor.getType())){
            return session.getVFSFileProvider().listFiles(aDescriptor);
        }
        DBSession dbs = new DBSession();
        try {
            DBFile dbf = (DBFile) dbs.createQuery("from DBFile where id=:id").
                    setInteger("id", Integer.parseInt(aDescriptor.getId())).
                    uniqueResult();
            if(dbf==null)
                return new FileDescriptor[0];
            List<DBFile> files = dbs.createQuery("from DBFile where parent=:parent and active=true").
                    setEntity("parent", dbf).list();
            List<FileDescriptor> result = new ArrayList<FileDescriptor>();
            for (DBFile dBFile : files) {
                FileDescriptor desc = getDefault();
                desc.setId(""+dBFile.getId());
                desc.setName(dBFile.getName());
                desc.setOwner(session.getLogin());
                desc.setParentFile(aDescriptor);
                desc.setType(dBFile.getType());
                if(VFSFileType.defaultInstance.getCode().equals(desc.getType())){
//                    desc.setProvider(session.getVFSFileProvider());
//                    desc.setAllowedFileTypes(new FileType[]{FolderFileType.DEFAULT_INSTANCE, FileManager.PLAIN_FILE_TYPE});
                    desc.setAllowedFileTypes(new FileType[]{});
                }
                getIcon(desc);
                result.add(desc);
            }
            return result.toArray(new FileDescriptor[0]);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
        return new FileDescriptor[0];
    }

    public InputStream getInputStream(FileDescriptor aDescriptor) throws FileException{
        DBSession dbs = new DBSession();
        try {
            DBFile dbf = (DBFile) dbs.get(DBFile.class, Integer.parseInt(aDescriptor.getId()));
            if(dbf!=null && dbf.isActive()){
                File file = new File(FileManager.FILE_STORAGE_PATH+"/"+dbf.getPath());
                if(file.exists() && !file.isDirectory())
                    return new FileInputStream(file);
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            if(t instanceof FileException)
                throw (FileException) t;
        } finally {
            dbs.close();
        }
        throw new FileException("Can't open file for reading");
    }

    public RandomAccessFile getRandomAccessFile(FileDescriptor aDescriptor, String aAccess)throws FileException{
        try {
            File file = getFile(aDescriptor);
            return new RandomAccessFile(file, aAccess);
        } catch (FileException t) {
            throw t;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't save file. Unknown error");
        }
    }

    private File getFile(FileDescriptor aDescriptor)throws FileException{
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            if(aDescriptor.getId()==null){
                //Create new file
                if(aDescriptor.getParentFile()!=null){
                    //Select parent first
                    DBFile dbf = (DBFile) dbs.createQuery("from DBFile where id=:id").
                            setInteger("id",
                                Integer.parseInt(aDescriptor.getParentFile().getId())).
                            uniqueResult();
                    if(dbf==null)
                        throw new FileException("Can't find parent file");
                    DBFile newFile = new DBFile();
                    newFile.setCreateDate(new Date());
                    newFile.setEditDate(new Date());
                    newFile.setName(aDescriptor.getName());
                    newFile.setOwner(session.getUser(dbs));
                    newFile.setType(aDescriptor.getType());
                    newFile.setParent(dbf);
                    String path = new RandomGUID().toString();
                    newFile.setPath(path);
                    dbs.save(newFile);
                    dbs.commit();
                    aDescriptor.setId(""+newFile.getId());
                    aDescriptor.setAllowedFileTypes(new FileType[]{FileManager.PLAIN_FILE_TYPE, VFSFileType.defaultInstance});
//                    Log.debug(this, "Opening output stream to "+path);
                    return new File(FileManager.FILE_STORAGE_PATH+"/"+path);
                }else
                    throw new FileException("Invalid parent file");
            }else{
                DBFile dbf = (DBFile) dbs.createQuery("from DBFile where id=:id and active=true").
                        setInteger("id",
                            Integer.parseInt(aDescriptor.getId())).
                        uniqueResult();
                dbf.setEditDate(new Date());
                dbf.setName(aDescriptor.getName());
                dbs.save(dbf);
                dbs.commit();
                return new File(FileManager.FILE_STORAGE_PATH+"/"+dbf.getPath());
            }
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            if(t instanceof FileException)
                throw (FileException)t;
            throw new FileException("Can't save file. Unknown error");
        } finally {
            dbs.close();
        }
    }

    public FileOutputStream getOutputStream(FileDescriptor aDescriptor)throws FileException {
        try {
            File file = getFile(aDescriptor);
            return new FileOutputStream(file);
        } catch (FileException t) {
            throw t;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't save file. Unknown error");
        }
    }

    private void removeActive(DBFile aDbf, DBSession aSession){
        aDbf.setActive(false);
        aSession.save(aDbf);
        List<DBFile> files = aSession.createQuery("from DBFile where parent=:parent").
                setEntity("parent", aDbf).list();
        for (DBFile dBFile : files) {
            removeActive(dBFile, aSession);
        }

    }

    private void removeReally(DBFile aDbf, DBSession aSession){
        List<DBFile> files = aSession.createQuery("from DBFile where parent=:parent").
                setEntity("parent", aDbf).list();
        for (DBFile dBFile : files) {
            removeReally(dBFile, aSession);
        }
        File file = new File(FileManager.FILE_STORAGE_PATH+"/"+aDbf.getPath());
        if(file.exists())
            file.delete();
        aSession.getSession().delete(aDbf);
//        aSession.save(aDbf);
    }

    public boolean removeFile(FileDescriptor aDescriptor, boolean aSafe) throws FileException {
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            DBFile dbf = (DBFile) dbs.get(DBFile.class, Integer.parseInt(aDescriptor.getId()));
//            if(dbf!=null && dbf.isActive()){
                if(aSafe)
                    removeActive(dbf, dbs);
                else
                    removeReally(dbf, dbs);
//            }
            dbs.commit();
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            if(t instanceof FileException)
                throw (FileException)t;
            throw new FileException("Can't remove file");
        } finally {
            dbs.close();
        }
        return true;
    }

    private FileDescriptor getDefault(){
        FileDescriptor descriptor = new FileDescriptor();
        descriptor.setEditable(true);
        descriptor.setProvider(this);
        descriptor.setRemovable(true);
        descriptor.setAllowedFileTypes(new FileType[]{FileManager.PLAIN_FILE_TYPE, VFSFileType.defaultInstance});
        return descriptor;
    }

    public FileDescriptor getRootFile() {
        DBSession dbs = new DBSession();
        try {
            DBFile dbf = (DBFile) dbs.createQuery("from DBFile where parent is null and owner=:user").
                    setEntity("user", session.getUser(dbs)).
                    uniqueResult();
            if(dbf==null){
                //Add new file
                dbf = new DBFile();
                dbf.setActive(true);
                dbf.setEditDate(new Date());
                dbf.setOwner(session.getUser(dbs));
                DBSession dbs2 = new DBSession();
                try {
                    dbs2.begin();
                    dbs2.save(dbf);
                    dbs2.commit();
                    return getRootFile();
                } catch (Throwable t) {
                    dbs2.rollback();
                    Log.error(this, "Exception thrown: " + t.getMessage());
                    t.printStackTrace();
                } finally {
                    dbs2.close();
                }
            }else{
                FileDescriptor descriptor = getDefault();
                descriptor.setOwner(session.getLogin());
                descriptor.setName("Documents");
                descriptor.setIcon("explorer_icn explorer_icn_files");
                descriptor.setEditable(false);
                descriptor.setRemovable(false);
                descriptor.setId(""+dbf.getId());
                return descriptor;
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
        return null;
    }

    public FileDescriptor getFileDescriptor(String aFileName)throws FileException{
        try {
            String[] files = aFileName.split("/");
            int pos = 0;
            FileDescriptor current = getRootFile();
            while(pos<files.length){
                String fileName = files[pos];
                if(fileName==null || "".equals(fileName.trim())){
                    pos++;
                    continue;
                }
                FileDescriptor[] descriptors = current.getProvider().listFiles(current);
                boolean found = false;
                for (FileDescriptor desc : descriptors) {
                    if(desc.getName().equalsIgnoreCase(fileName)){
                        found = true;
                        current = desc;
                    }
                }
                if(!found){
                    return null;
                }
                pos++;
            }
            return current;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't find file "+aFileName);
        }
    }

    public FileDescriptor createPathToFileDescriptor(String aFileName)throws FileException{
        try {
            String[] files = aFileName.split("/");
            int pos = 0;
            FileDescriptor current = getRootFile();
            while(pos<files.length){
                String fileName = files[pos];
                if(fileName==null || "".equals(fileName.trim())){
                    pos++;
                    continue;
                }
                FileDescriptor[] descriptors = current.getProvider().listFiles(current);
                boolean found = false;
//                if(pos==files.length-1){
//                    Log.debug(this, "createPathToFileDescriptor lastfile", fileName);
//                    for (FileDescriptor fileDescriptor : descriptors) {
//                        Log.debug(this, "File: "+fileDescriptor.getName());
//                    }
//                }
                for (FileDescriptor desc : descriptors) {
                    if(desc.getName().equalsIgnoreCase(fileName)){
                        found = true;
                        current = desc;
                    }
                }
                if(!found){
                    FileDescriptor newFile = new FileDescriptor();
                    newFile.setName(fileName);
                    newFile.setParentFile(current);
                    newFile.setProvider(current.getProvider());
                    if(pos==files.length-1){
                        //Last file
//                        Log.debug(this, "createPathToFileDescriptor Created new file "+fileName);
                        return newFile;
                    }else{
                        newFile.getProvider().createFolder(newFile);
                        current = newFile;
                    }
                }
                pos++;
            }
//            Log.debug(this, "createPathToFileDescriptor Existing file "+aFileName+", "+current.getName()+", "+current.getId());
            return current;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't path to file "+aFileName);
        }
    }

    public void createFolder(FileDescriptor aDescriptor) throws FileException {
        if(aDescriptor.getParentFile()==null)
            throw new FileException("Can't create folder: parent file isn't defined");
        if(aDescriptor.getParentFile().getProvider()!=this)
            throw new FileException("Can't create folder: invalid parent file");
        try {
            aDescriptor.setType("text/plain");
            OutputStream os = aDescriptor.getParentFile().getProvider().getOutputStream(aDescriptor);
            os.close();
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't create folder: error "+t.getMessage());
        }
    }

    public void renameFile(FileDescriptor aDescriptor) throws FileException {
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            DBFile dbf = (DBFile) dbs.createQuery("from DBFile where id=:id and active=true").
                    setInteger("id",
                        Integer.parseInt(aDescriptor.getId())).
                    uniqueResult();
            List<DBFile> neighbours = dbs.createQuery("from DBFile where parent=:parent and active=true").
                    setEntity("parent", dbf.getParent()).list();
            for (DBFile n : neighbours) {
//                Log.debug(this, "Rename file, looking for existing: "+n.getName(), n.getId(), aDescriptor.getName(), dbf.getId());
                if(n.getName().equalsIgnoreCase(aDescriptor.getName()) && n.getId()!=dbf.getId()){
                    //Remove previous files
                    removeReally(n, dbs);
//                    Log.debug(this, "File removed");
                }
            }
            dbf.setEditDate(new Date());
            dbf.setName(aDescriptor.getName());
            dbs.save(dbf);
            dbs.commit();
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            throw new FileException("Can't rename file "+aDescriptor.getName());
        } finally {
            dbs.close();
        }
    }
}
